package com.rizzo.back.util.search;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Query;

import java.io.Serializable;
import java.util.*;

/**
 * TODO Insert JavaDoc!
 */
public class SearchDataObject implements Serializable {

    private boolean highlighted;

    private int highlightFragmentLength;

    private int maxNumberOfHighlightFragments;

    private boolean synonymSearch;

    private float synonymBoost;

    private AnalyzerType synonymAnalyzer;

    private Map<AnalyzerType, Map<String, Float>> queryInfo;

    private String queryString;

    /**
     * Constructor SearchDataObject creates a new SearchDataObject instance.
     *
     * @param highlighted of type boolean
     * @param highlightFragmentLength of type int
     * @param maxNumberOfHighlightFragments of type int
     * @param synonymSearch of type boolean
     * @param synonymBoost of type float
     * @param synonymAnalyzer of type AnalyzerType
     * @param queryInfo of type Map<AnalyzerType, Map<String, Float>>
     * @param queryString of type String
     */
    public SearchDataObject(boolean highlighted, int highlightFragmentLength, int maxNumberOfHighlightFragments, boolean synonymSearch, float synonymBoost, AnalyzerType synonymAnalyzer, Map<AnalyzerType, Map<String, Float>> queryInfo, String queryString) {
        this.highlighted = highlighted;
        this.highlightFragmentLength = highlightFragmentLength;
        this.maxNumberOfHighlightFragments = maxNumberOfHighlightFragments;
        this.synonymSearch = synonymSearch;
        this.synonymBoost = synonymBoost;
        this.synonymAnalyzer = synonymAnalyzer;
        this.queryInfo = queryInfo;
        this.queryString = queryString;
    }

    /**
     * Method isHighlighted returns the highlighted of this SearchDataObject object.
     *
     * @return the highlighted (type boolean) of this SearchDataObject object.
     */
    public boolean isHighlighted() {
        return highlighted;
    }

    public int getHighlightFragmentLength() {
        return highlightFragmentLength;
    }

    public int getMaxNumberOfHighlightFragments() {
        return maxNumberOfHighlightFragments;
    }

    public boolean isSynonymSearch() {
        return synonymSearch;
    }

    public float getSynonymBoost() {
        return synonymBoost;
    }

    public AnalyzerType getSynonymAnalyzer() {
        return synonymAnalyzer;
    }

    /**
     * Method getQueryInfo returns the queryInfo of this SearchDataObject object.
     *
     * @return the queryInfo (type Map<AnalyzerType, Map<String, Float>>) of this SearchDataObject object.
     */
    public Map<AnalyzerType, Map<String, Float>> getQueryInfo() {
        return queryInfo;
    }

    /**
     * Method getQueryString returns the queryString of this SearchDataObject object.
     *
     * @return the queryString (type String) of this SearchDataObject object.
     */
    public String getQueryString() {
        return queryString;
    }

    /**
     * Method getCombinedQuery creates a combined Query of all the distinct {@link AnalyzerType} and its according fields and boosters.
     * This method is handy for a global search of different fields of an entity.
     * 
     * @return the combinedQuery (type Query) of this SearchDataObject object.
     * @throws ClassNotFoundException when
     * @throws InstantiationException when
     * @throws IllegalAccessException when
     * @throws ParseException when
     */
    public Query getCombinedQuery() throws ClassNotFoundException, InstantiationException, IllegalAccessException, ParseException {
        LinkedList<Query> queries = new LinkedList<Query>();
        for (Map.Entry<AnalyzerType, Map<String, Float>> analyzerTypeMapEntry : queryInfo.entrySet()) {
            QueryParser parser = new MultiFieldQueryParser(
                this.getFields(),
                analyzerTypeMapEntry.getKey().getInstantiation(),
                analyzerTypeMapEntry.getValue()
            );
            queries.add(parser.parse(this.queryString));
        }
        Query combinedQuery = null;
        if(queries.size() > 1) {
            Query firstQuery = queries.get(0);
            queries.removeFirst();
            combinedQuery = firstQuery.combine((Query[]) queries.toArray());
        } else if(queries.size() == 1){
            combinedQuery = queries.get(0);
        }
        return combinedQuery;
    }

    /**
     * Method getFields returns the fields of this SearchDataObject object.
     *
     * @return the fields (type String[]) of this SearchDataObject object.
     */
    public String[] getFields(){
        List<String> fields = new ArrayList<String>();
        for (Map.Entry<AnalyzerType, Map<String, Float>> analyzerTypeMapEntry : queryInfo.entrySet()) {
            for (Map.Entry<String, Float> fieldBoostEntry : analyzerTypeMapEntry.getValue().entrySet()) {
                fields.add(fieldBoostEntry.getKey());
            }
        }
        String[] fieldsArray = new String[fields.size()];
        for (int i = 0; i < fields.size(); i++) {
            fieldsArray[i] = fields.get(i);
        }
        return fieldsArray;
    }

    /**
     * Method getAnalyzers returns the analyzers of this SearchDataObject object.
     *
     * @return the analyzers (type Set<Analyzer>) of this SearchDataObject object.
     * @throws ClassNotFoundException when
     * @throws InstantiationException when
     * @throws IllegalAccessException when
     */
    public Set<Analyzer> getAnalyzers() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Set<Analyzer> analyzers = new HashSet<Analyzer>();
        for (AnalyzerType analyzerType : queryInfo.keySet()) {
            analyzers.add(analyzerType.getInstantiation());
        }
        return analyzers;
    }

}
